package org.loong.crypto.core.algorithm;

import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import org.loong.crypto.core.Algorithm;
import org.loong.crypto.core.AlgorithmFamily;

/**
 * The Key algorithm.
 */
public final class KeyAlgorithm extends Algorithm {

    /**
     * The key length, zero if not specified.
     */
    private int keyLength;

    /**
     * AES algorithm.
     */
    public static final KeyAlgorithm AES = new KeyAlgorithm("AES", 128);

    /**
     * Blowfish algorithm.
     */
    public static final KeyAlgorithm BLOWFISH = new KeyAlgorithm("Blowfish", 128);

    /**
     * DES algorithm.
     */
    public static final KeyAlgorithm DES = new KeyAlgorithm("DES", 64);

    /**
     * DESede algorithm.
     */
    public static final KeyAlgorithm DESEDE = new KeyAlgorithm("DESede", 128);

    /**
     * HmacMD5 algorithm.
     */
    public static final KeyAlgorithm HMAC_MD5 = new KeyAlgorithm("HmacMD5", 128);

    /**
     * HMAC SHA-1 algorithm.
     */
    public static final KeyAlgorithm HMAC_SHA1 = new KeyAlgorithm("HmacSHA1", 128);

    /**
     * HMAC SHA-224 algorithm.
     */
    public static final KeyAlgorithm HMAC_SHA224 = new KeyAlgorithm("HmacSHA224", 224);

    /**
     * HMAC SHA-256 algorithm.
     */
    public static final KeyAlgorithm HMAC_SHA256 = new KeyAlgorithm("HmacSHA256", 256);

    /**
     * HMAC SHA-384 algorithm.
     */
    public static final KeyAlgorithm HMAC_SHA384 = new KeyAlgorithm("HmacSHA384", 384);

    /**
     * HMAC SHA-512 algorithm.
     */
    public static final KeyAlgorithm HMAC_SHA512 = new KeyAlgorithm("HmacSHA512", 512);

    /**
     * RC2 algorithm.
     */
    public static final KeyAlgorithm RC2 = new KeyAlgorithm("RC2", 128);

    /**
     * RC4 algorithm.
     */
    public static final KeyAlgorithm RC4 = new KeyAlgorithm("RC4", 128);

    /**
     * SM4 algorithm.
     */
    public static final KeyAlgorithm SM4 = new KeyAlgorithm("SM4", 128);

    /**
     * The supported key algorithms.
     */
    public static final Map<String, KeyAlgorithm> SUPPORTED_ALGORITHMS;

    static {
        Map<String, KeyAlgorithm> algs = new LinkedHashMap<>();
        algs.put(AES.getName(), AES);
        algs.put(BLOWFISH.getName(), BLOWFISH);
        algs.put(DES.getName(), DES);
        algs.put(DESEDE.getName(), DESEDE);
        algs.put(HMAC_MD5.getName(), HMAC_MD5);
        algs.put(HMAC_SHA1.getName(), HMAC_SHA1);
        algs.put(HMAC_SHA224.getName(), HMAC_SHA224);
        algs.put(HMAC_SHA256.getName(), HMAC_SHA256);
        algs.put(HMAC_SHA384.getName(), HMAC_SHA384);
        algs.put(HMAC_SHA512.getName(), HMAC_SHA512);
        algs.put(RC2.getName(), RC2);
        algs.put(RC4.getName(), RC4);
        algs.put(SM4.getName(), SM4);
        SUPPORTED_ALGORITHMS = Collections.unmodifiableMap(algs);
    }

    /**
     * Key algorithm family.
     */
    public static final class Family extends AlgorithmFamily<KeyAlgorithm> {

        private static final long serialVersionUID = 1L;

        /**
         * Creates a new key algorithm family.
         *
         * @param algs The key algorithms of the family. Must not be {@code null}.
         */
        public Family(final KeyAlgorithm... algs) {
            super(algs);
        }
    }

    /**
     * Creates a new key algorithm name.
     * 
     * @param name the algorithm name. Must not be {@code null}.
     */
    public KeyAlgorithm(final String name) {
        this(name, 0);
    }

    /**
     * Creates a new key algorithm name.
     * 
     * @param name the algorithm name. Must not be {@code null}.
     * @param keyLength the key length, zero if not specified.
     */
    public KeyAlgorithm(String name, final int keyLength) {
        super(name);

        this.keyLength = keyLength;
    }

    /**
     * Gets the value of keyLength.
     *
     * @return the value of keyLength
     */
    public int getKeyLength() {
        return keyLength;
    }

    /**
     * Parses a key algorithm from the specified string.
     *
     * @param name the algorithm name. Must not be {@code null}.
     * @return the key algorithm.
     * @throws NoSuchAlgorithmException the ${@link NoSuchAlgorithmException}
     */
    public static KeyAlgorithm find(final String name) throws NoSuchAlgorithmException {
        KeyAlgorithm algorithm = SUPPORTED_ALGORITHMS.get(name);
        if (algorithm == null) {
            throw new NoSuchAlgorithmException("Unknown key algorithm type requested: " + name);
        }

        return algorithm;
    }
}
